WebGL 纹理加载时序与格式选型
一句话结论:纹理必须在图像真正可读之后上传,跨域图片要 CORS 与 crossOrigin 同时配置,格式按通道需求选择才能兼顾画质与显存。
图像跨域与纹理加载时序
是什么
图像跨域指图片来自与页面不同的源,浏览器出于安全策略限制其像素读取;加载时序指纹理上传必须发生在图像解码完成、且像素可读之后,否则 texImage2D 会失败或得到空白纹理。
解决什么问题
它解决贴图不显示与控制台报安全错误的问题。跨域图片若没有正确的 CORS 头,即使图像能显示,调用 texImage2D 也会被拒绝;上传过早则拿到未解码的空图像,两种情况都表现为画面异常却不易定位。
为什么这样做
WebGL 能访问纹理像素,若允许任意跨域图像上传,就等于绕过了同源策略读取其他站点内容。因此浏览器要求服务端显式授权(CORS 头),客户端也必须在请求阶段声明跨域模式,让响应带出授权信息后像素才可读。
怎么用
function loadTexture(gl, url) {
return new Promise((resolve, reject) => {
const img = new Image();
img.crossOrigin = 'anonymous'; // 请求阶段声明跨域
img.onload = () => {
const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
resolve(tex);
};
img.onerror = reject;
img.src = url;
});
}
// 所有纹理就绪后再启动渲染循环
Promise.all([loadTexture(gl, a), loadTexture(gl, b)]).then(() => requestAnimationFrame(render));
常见坑
- 只设 crossOrigin 而服务端无 CORS 头 → 原因:授权需双方配合 → 处理:让图片服务返回 Access-Control-Allow-Origin
- 在 onload 之前上传 → 原因:图像尚未解码 → 处理:把上传逻辑放进 onload 或使用 decode() 等待
- 缓存导致 crossOrigin 失效 → 原因:同一 URL 先被无跨域请求缓存 → 处理:统一跨域设置或加查询参数区分
- 首帧直接渲染未就绪物体 → 原因:采样空纹理 → 处理:用占位纹理或等全部资源就绪再绘制
纹理格式与颜色表示
是什么
纹理格式描述像素在显存中的通道构成与每像素字节数。常用内部格式有 RGBA、RGB、LUMINANCE、ALPHA 与 LUMINANCE_ALPHA,WebGL 2.0 还提供更丰富的 R8、RG8、RGB16F 等 sized 格式。
解决什么问题
它解决显存与带宽的取舍问题。一张 1024×1024 的 RGBA 纹理占 4MB,若只需要灰度遮罩,改用 LUMINANCE 可降到 1MB;格式选错会白白浪费显存与采样带宽,移动端尤其明显。
为什么这样做
显存容量与带宽是移动端最紧张的资源。纹理是单帧中体积最大的资源类型,按实际需要的通道数选择格式,能在几乎不损失画质的前提下成倍降低占用,也让纹理缓存命中率更高。
怎么用
// 彩色贴图
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img);
// 灰度遮罩:只需要亮度通道,节省 3/4 显存
gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, width, height, 0,
gl.LUMINANCE, gl.UNSIGNED_BYTE, grayData);
// 采样时按格式取通道
| 格式 | 每像素 | 适用 |
|---|---|---|
| RGBA | 4 字节 | 彩色贴图、带透明 |
| RGB | 3 字节 | 无透明彩色贴图 |
| LUMINANCE | 1 字节 | 灰度、高度、遮罩 |
| ALPHA | 1 字节 | 仅透明度遮罩 |
| LUMINANCE_ALPHA | 2 字节 | 灰度加透明,如字体 |
常见坑
- 用 RGBA 存单通道数据 → 原因:习惯性默认 → 处理:按通道需求选 LUMINANCE 或 ALPHA
- 半透明边缘发黑 → 原因:预乘 Alpha 不一致 → 处理:统一 premultipliedAlpha 设置或手动预乘
- 灰度图采样后颜色不对 → 原因:只写了 r 通道 → 处理:着色器中把 r 赋给三个通道
- 忽略颜色空间 → 原因:贴图为 sRGB 却按线性计算 → 处理:统一做 sRGB 到线性的转换
要点总结
- 跨域纹理需要 crossOrigin 与服务端 CORS 头同时配置
- 上传必须发生在图像 onload 或 decode 完成之后
- 首帧渲染要等纹理就绪,或先用占位纹理
- 按通道需求选格式:彩色 RGBA、灰度 LUMINANCE、仅透明 ALPHA
- 预乘 Alpha 设置要与纹理数据一致,避免半透明边缘发黑
